//-----------------------------------------------------------------------
// <copyright file="Export.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
//-----------------------------------------------------------------------
namespace Microsoft.Sdc.Tasks.BizTalk2006
{
    using System.Collections;
    using System.IO;
    using System.Xml;
    using System.Xml.XPath;
    using System.Xml.Xsl;
    using Build.Framework;
    using BizTalk2004;
    using BizTalkOM;

    /// <summary>
    /// Exports a deployed BizTalk system to a Proj file. This can then be used to start, stop, deploy or undeploy the
    /// system. It also supports only exporting specific applications.
    /// The export will not include any system applications, and these should not be undeployed.
    /// </summary>
    /// <remarks>
    /// 	<code>
    /// 		<![CDATA[<Microsoft.Sdc.Tasks.BizTalk2006.Export SourceFolder="Path to assemblies to deploy" OutputFile="file to write the results to" Assemblies="list of assemblies" Applications="list of applications" IncludeReferencedApplications="boolean"/>]]>
    /// 	</code>
    /// 	<para>where:</para>
    /// 	<para><i>SourceFolder (Required)</i></para>
    /// 	<para>
    /// The source folder where the assemblies are to be deployed from. This path is only used in the output file, so
    /// can be a parameter that gets replaced before use.
    /// </para>
    /// 	<para><i>OutputFile</i></para>
    /// 	<para>
    /// The name of the file to write the resulting project file to. Supports relative and full paths. The file name
    /// must be given.
    /// </para>
    /// 	<para><i>Assemblies</i></para>
    /// 	<para>
    /// A list of the fully qualified names of the assemblies to deploy. It is used to determine the assembly dependencies. The list needs to be in the format:
    /// Name, Version, Culture, PublicKeyToken. For example:
    /// Microsoft.Samples.BizTalk.SouthridgeVideo.OrderBrokerSchemas, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1fb25adc93e1ab92
    /// </para>
    /// The framework task GetAssemblyIdentity can be used to get this list, as shown in the sample below.
    /// <para><i>Applications</i></para>
    /// 	<para>
    /// The list of applications to export.
    /// </para>
    /// 	<para><i>IncludeReferencedApplications</i></para>
    /// 	<para>
    /// 		<b>true</b> to also export referenced applications, even if they are not in the list of application, or <b>false</b> otherwise. The default is <b>true</b>.
    /// The system applications will never be exported.
    /// </para>
    /// 	<para><i>logPath</i></para>
    /// 	<para>
    /// The path to the log file for the installation. The default is no log file.
    /// </para>
    /// 	<para><i>server</i></para>
    /// 	<para>
    /// The logical name of the server hosting the BizTalk management database. If neither this nor the
    /// database name are specified then the installation will default to the local installation.
    /// </para>
    /// 	<para><i>database</i></para>
    /// 	<para>
    /// The name of the BizTalk management database. If neither this nor the server name are specified
    /// then the installation will default to the local installation.
    /// </para>
    /// </remarks>
    /// <example>
    /// 	<code><![CDATA[
    /// <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Export">
    /// <UsingTask TaskName="Microsoft.Sdc.Tasks.BizTalk2006.Export" AssemblyFile="D:\Projects\Microsoft.Sdc.Tasks\Main\Src\Solutions\Main\Tasks.BizTalk\bin\Debug\Microsoft.Sdc.Tasks.BizTalk.dll" />
    /// <ItemGroup>
    /// <Assemblies Include="C:\Program Files\Microsoft BizTalk Server 2006\SDK\Scenarios\BPM\MainAssemblies\*.dll"/>
    /// </ItemGroup>
    /// <ItemGroup>
    /// <IncludedApplications Include="BTSScn.BPM.CableOrderApp.Test;BTSScn.BPM.OrderBrokerApp.Test;BTSScn.BPM.MessagingApp.Test;BTSScn.BPM.MessagingApp;BTSScn.BPM.CableOrderApp;BTSScn.BPM.OrderBrokerApp"/>
    /// </ItemGroup>
    /// <Target Name="RetrieveAssemblyFQN">
    /// <GetAssemblyIdentity AssemblyFiles="@(Assemblies)">
    /// <Output TaskParameter="Assemblies" ItemName="AssembliesFQN"/>
    /// </GetAssemblyIdentity>
    /// <!--Message Text="@(AssembliesFQN)"/-->
    /// </Target>
    /// <Target Name="Export" DependsOnTargets="RetrieveAssemblyFQN">
    /// <Microsoft.Sdc.Tasks.BizTalk2006.Export
    /// OutputFile="D:\Projects\Microsoft.Sdc.Tasks\Main\Src\SBF\Main\Src\Build\DeployBizTalk.proj"
    /// SourceFolder="C:\Program Files\Microsoft BizTalk Server 2006\SDK\Scenarios\BPM\MainAssemblies"
    /// Assemblies="@(AssembliesFQN)"
    /// Applications="@(IncludedApplications)"
    /// IncludeReferencedApplications="true"/>
    /// </Target>
    /// </Project>
    /// ]]></code>
    /// </example>
    public class Export : BizTalk2004TaskBase
    {
        private static readonly string[] sortOrderLetters = new string[10] { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" };
        private ArrayList assemblyDependencyList = new ArrayList();
        private ArrayList orchDependencyList = new ArrayList();
        private ArrayList applicationDependencyList = new ArrayList();

        private ITaskItem[] assemblies;
        private string outputFile;
        private string sourceFolder;
        private string[] applications;
        private bool includeReferencedApplications = true;

        /// <summary>
        /// Gets or sets the source folder where the assemblies are deployed from.
        /// </summary>
        /// <value>The source folder that contains the assemblies to deploy or undeploy.</value>
        [Required]
        public string SourceFolder
        {
            get { return this.sourceFolder; }
            set { this.sourceFolder = value; }
        }

        /// <summary>
        /// Gets or sets the output file.
        /// </summary>
        /// <value>The file to write the output file to.</value>
        [Required]
        public string OutputFile
        {
            get { return this.outputFile; }
            set { this.outputFile = value; }
        }

        /// <summary>
        /// Gets or sets the assemblies to deploy or undeploy
        /// </summary>
        /// <value>The assemblies.</value>
        [Required]
        public ITaskItem[] Assemblies
        {
            get { return this.assemblies; }
            set { this.assemblies = value; }
        }

        /// <summary>
        /// Gets or sets the applications to export. If this is not specified, all the 
        /// applications on the system (other than the system ones) will be exported.
        /// Referenced applications are exported if the attribue IncludeReferencedApplications is set.
        /// </summary>
        public string[] Applications
        {
            get { return this.applications; }
            set { this.applications = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to include referenced applications when exporting
        /// applications.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if referenced applications should be included. Default value; otherwise, <c>false</c>.
        /// </value>
        public bool IncludeReferencedApplications
        {
            get { return this.includeReferencedApplications; }
            set { this.includeReferencedApplications = value; }
        }

        /// <summary>
        /// This is the main execute method that all tasks should implement
        /// </summary>
        /// <remarks>
        /// TaskException should be thrown in the event of errors
        /// </remarks>
        protected override void InternalExecute()
        {
            string template = "Microsoft.Sdc.Tasks.BizTalk._2006.CreateProjFile.xslt";
            string emptyTemplate = "Microsoft.Sdc.Tasks.BizTalk._2006.CreateEmptyProjFile.xslt";

            ArrayList modifiedAssemblies = new ArrayList();
            if (this.Assemblies.Length == 0)
            {
                XmlDocument emptyDoc = new XmlDocument();
                emptyDoc.LoadXml("<root/>");
                WriteOutputFile(emptyTemplate, emptyDoc, /*null, null, null,*/ null, this.OutputFile);
                Log.LogMessage("Empty target file written");
                return;
            }

            foreach (ITaskItem item in this.assemblies)
            {
                modifiedAssemblies.Add(item.ItemSpec);
            }

            BizTalkInstallation installation = new BizTalkInstallation();
            if (string.IsNullOrEmpty(this.Server) || string.IsNullOrEmpty(this.Database))
            {
                installation.InitExplorer();
            }
            else
            {
                installation.Server = this.Server;
                installation.MgmtDatabaseName = this.Database;
            }

            if (this.Applications != null && this.Applications.Length > 0)
            {
                ArrayList appList = new ArrayList(this.Applications);
                installation.LoadConfig(appList, this.IncludeReferencedApplications);
            }
            else
            {
                installation.LoadConfig();
            }

            XmlDocument config = new XmlDocument();
            config.LoadXml(installation.GetXml());
            config.Save("config.xml");

            WriteOutputFile(template, config, this.SourceFolder, this.outputFile);
            Log.LogMessage("Export Configuration Done");
            return;
        }

        /// <summary>
        /// Writes the output file.
        /// </summary>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="doc">The doc.</param>
        /// <param name="sourceFolder">The source folder.</param>
        /// <param name="outputFile">The output file.</param>
        private static void WriteOutputFile(string templateName, XmlDocument doc, string sourceFolder, string outputFile)
        {
            System.Reflection.Assembly thisExe = System.Reflection.Assembly.GetExecutingAssembly();
            Stream s = thisExe.GetManifestResourceStream(templateName);
            XmlTextReader reader = new XmlTextReader(s);
            XslCompiledTransform trans = new XslCompiledTransform();
            trans.Load(reader);
            XmlTextWriter writer = new XmlTextWriter(outputFile, System.Text.Encoding.UTF8);
            XsltArgumentList argList = new XsltArgumentList();

            if (sourceFolder != null)
            {
                argList.AddParam("assemblyRootFolder", string.Empty, sourceFolder);
            }

            trans.Transform(doc, argList, writer);
            writer.Close();
        }

        private XPathNavigator GetAssemblyDependencyList(XmlDocument configDoc, ArrayList modifiedAssemblies)
        {
            XmlNodeList assembliesList = configDoc.SelectNodes(@"/BizTalkBaseObject/Applications/Application/Assemblies/Assembly");
            foreach (XmlNode assembly in assembliesList)
            {
                string name = assembly.SelectSingleNode("DisplayName").InnerText;
                if ((modifiedAssemblies != null && modifiedAssemblies.Contains(name)) || (modifiedAssemblies == null))
                {
                    if (!this.assemblyDependencyList.Contains(name))
                    {
                        this.AddDependentAssemblies(name, configDoc);
                    }
                }
            }

            XmlElement depList = configDoc.CreateElement("AssemblyDependencyList");
            int i = 0;
            foreach (string name in this.assemblyDependencyList)
            {
                XmlNode n = configDoc.SelectSingleNode(string.Format("/BizTalkBaseObject/Applications/Application/Assemblies/Assembly[DisplayName='{0}']", name));
                string applicationName = n.SelectSingleNode(string.Format("parent::*/parent::*/Name")).InnerText;
                XmlNode newN = n.CloneNode(true);
                XmlElement appElement = configDoc.CreateElement("Application");
                appElement.InnerText = applicationName;
                newN.AppendChild(appElement);
                depList.AppendChild(newN);
                XmlAttribute a = configDoc.CreateAttribute("sortOrder");
                string sort = i.ToString("D3");
                string finalSort = string.Empty;
                for (int j = 0; j < sort.Length; j++)
                {
                    finalSort += sortOrderLetters[int.Parse(sort[j].ToString())];
                }

                i++;
                a.Value = finalSort;
                newN.Attributes.Append(a);
            }

            XPathNavigator nav = depList.CreateNavigator();
            return nav;
        }

        private void AddDependentAssemblies(string assemblyName, XmlDocument configDoc)
        {
            string xpath = string.Format(@"/BizTalkBaseObject/Applications/Application/Assemblies/Assembly[References/ReferencedAssembly='{0}']/DisplayName", assemblyName);
            XmlNodeList dependentAss = configDoc.SelectNodes(xpath);
            
            // if this has nothing that depends on it, add it to the list
            if (dependentAss.Count == 0)
            {
                this.assemblyDependencyList.Add(assemblyName);
            }
            else
            {
                foreach (XmlNode refNode in dependentAss)
                {
                    string currentAssName = refNode.InnerText;
                    if (!this.assemblyDependencyList.Contains(currentAssName))
                    {
                        this.AddDependentAssemblies(currentAssName, configDoc);
                    }
                }

                if (!this.assemblyDependencyList.Contains(assemblyName))
                {
                    this.assemblyDependencyList.Add(assemblyName);
                }
            }
        }

        /// <summary>
        /// Gets the orchestration dependency list.
        /// </summary>
        /// <param name="configDoc">The config doc.</param>
        /// <param name="modifiedAssemblies">The modified assemblies.</param>
        /// <returns>XPathNavigator</returns>
        private XPathNavigator GetOrchestrationDependencyList(XmlDocument configDoc, ArrayList modifiedAssemblies)
        {
            XmlNodeList orchList = configDoc.SelectNodes(@"/BizTalkBaseObject/Applications/Application/Orchestrations/Orchestration");
            foreach (XmlNode orch in orchList)
            {
                string simpleName = orch.SelectSingleNode("Name").InnerText;
                string assemblyName = orch.SelectSingleNode("ParentAssembly/Name").InnerText;
                if ((modifiedAssemblies != null && modifiedAssemblies.Contains(assemblyName)) || (modifiedAssemblies == null))
                {
                    if (!this.orchDependencyList.Contains(simpleName))
                    {
                        this.AddDependentOrchestrations(simpleName, configDoc);
                    }
                }
            }

            XmlElement depList = configDoc.CreateElement("OrchestrationDependencyList");
            int i = 0;
            foreach (string name in this.orchDependencyList)
            {
                XmlNode n = configDoc.SelectSingleNode(string.Format("/BizTalkBaseObject/Applications/Application/Orchestrations/Orchestration[Name='{0}']", name));
                string appName = n.SelectSingleNode("parent::*/parent::*/Name").InnerText;
                XmlNode newN = n.CloneNode(true);
                XmlElement applicationElement = configDoc.CreateElement("Application");
                applicationElement.InnerText = appName;
                newN.AppendChild(applicationElement);
                depList.AppendChild(newN);
                XmlAttribute a = configDoc.CreateAttribute("sortOrder");
                string sort = i.ToString("D3");
                string finalSort = string.Empty;
                for (int j = 0; j < sort.Length; j++)
                {
                    finalSort += sortOrderLetters[int.Parse(sort[j].ToString())];
                }

                i++;
                a.Value = finalSort;
                newN.Attributes.Append(a);
            }

            XPathNavigator nav = depList.CreateNavigator();
            return nav;
        }

        /// <summary>
        /// Adds the dependent orchestrations.
        /// </summary>
        /// <param name="orchName">Name of the orch.</param>
        /// <param name="configDoc">The config doc.</param>
        private void AddDependentOrchestrations(string orchName, XmlDocument configDoc)
        {
            string xpath = string.Format(@"/BizTalkBaseObject/Applications/Application/Assemblies/Assembly/Orchestrations/Orchestration[InvokedOrchestrations/Orchestration/Name='{0}']/Name", orchName);
            XmlNodeList dependentOrch = configDoc.SelectNodes(xpath);
            
            // if this has nothing that depends on it, add it to the list
            if (dependentOrch.Count == 0)
            {
                this.orchDependencyList.Add(orchName);
            }
            else
            {
                foreach (XmlNode refNode in dependentOrch)
                {
                    string currentOrchName = refNode.InnerText;
                    if (!this.orchDependencyList.Contains(currentOrchName))
                    {
                        this.AddDependentOrchestrations(currentOrchName, configDoc);
                    }
                }

                if (!this.orchDependencyList.Contains(orchName))
                {
                    this.orchDependencyList.Add(orchName);
                }
            }
        }

        /// <summary>
        /// Gets the application dependency list.
        /// </summary>
        /// <param name="configDoc">The config doc.</param>
        /// <param name="modifiedAssemblies">The modified assemblies.</param>
        /// <returns>XPathNavigator</returns>
        private XPathNavigator GetApplicationDependencyList(XmlDocument configDoc, ArrayList modifiedAssemblies)
        {
            XmlNodeList appList = configDoc.SelectNodes(@"/BizTalkBaseObject/Applications/Application");
            foreach (XmlNode appNode in appList)
            {
                string appName = appNode.SelectSingleNode("Name").InnerText;
                if (!this.applicationDependencyList.Contains(appName))
                {
                    this.AddDependentApplications(appName, configDoc);
                }
            }

            XmlElement appDependencyList = configDoc.CreateElement("ApplicationDependencyList");
            int i = 0;
            foreach (string appName in this.applicationDependencyList)
            {
                XmlNode appNode = configDoc.SelectSingleNode(string.Format("/BizTalkBaseObject/Applications/Application[Name='{0}']", appName));
                XmlNode newAppNode = appNode.CloneNode(true);
                appDependencyList.AppendChild(newAppNode);
                XmlAttribute sortAttrib = configDoc.CreateAttribute("sortOrder");
                string sort = i.ToString("D3");
                string finalSort = string.Empty;
                for (int j = 0; j < sort.Length; j++)
                {
                    finalSort += sortOrderLetters[int.Parse(sort[j].ToString())];
                }

                i++;
                sortAttrib.Value = finalSort;
                newAppNode.Attributes.Append(sortAttrib);
            }

            XPathNavigator nav = appDependencyList.CreateNavigator();
            return nav;
        }

        private void AddDependentApplications(string applicationName, XmlDocument configDoc)
        {
            string xpath = string.Format(@"/BizTalkBaseObject/Applications/Application[ReferencedApplications/Application/Name='{0}']/Name", applicationName);
            XmlNodeList dependentApp = configDoc.SelectNodes(xpath);
            if (dependentApp.Count == 0)
            {
                this.applicationDependencyList.Add(applicationName);
            }
            else
            {
                foreach (XmlNode refNode in dependentApp)
                {
                    string currentAppName = refNode.InnerText;
                    if (!this.applicationDependencyList.Contains(currentAppName))
                    {
                        this.AddDependentApplications(currentAppName, configDoc);
                    }
                }

                if (!this.applicationDependencyList.Contains(applicationName))
                {
                    this.applicationDependencyList.Add(applicationName);
                }
            }
        }
    }
}
